package personal.scanner;


import personal.util.Map2D;
import personal.util.Pair;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class SimpleScannerDFA implements ScannerDFA {
    @Override
    public int size() {
        return this.size;
    }

    @Override
    public int startId() {
        return this.startId;
    }

    @Override
    public boolean isInTransitions(int transit) {
        return this.transitions.contains(transit);
    }

    @Override
    public int transitTo(int u, int transit) throws NullPointerException {
        return this.adj.get(u, transit);
    }

    @Override
    public boolean isAcceptable(int u) {
        return this.acceptType.containsKey(u);
    }

    @Override
    public Class getAcceptType(int u) {
        return this.acceptType.get(u);
    }

    public Class defaultException(int u) {
        return this.defaultExceptionType.get(u);
    }


    /**
     * 最大节点编号
     */
    private int size;
    /**
     * 开始节点编号
     */
    private final int startId;

    /**
     * 邻接表
     */
    private final Map2D<Integer, Integer, Integer> adj; // <u, w, v>

    /**
     * 转移表
     */
    private final Set<Integer> transitions;

    /**
     * 接受类型
     */
    private final Map<Integer, Class> acceptType;
    /**
     * 默认异常类型
     */
    private final Map<Integer, Class> defaultExceptionType;

    /**
     * 注册节点接受类型，返回是否注册成功
     *
     * @param u    节点编号
     * @param type 接受类型
     * @return 是否注册成功
     */
    public boolean registerAcceptableNode(int u, Class type) {
        return this.acceptType.putIfAbsent(u, type) == null;
    }

    /**
     * 注册节点的默认异常类型，返回是否注册成功
     *
     * @param u    节点编号
     * @param type 默认异常类型
     * @return 是否注册成功
     */
    public boolean registerDefaultException(int u, Class type) {
        return this.defaultExceptionType.putIfAbsent(u, type) == null;
    }


    /**
     * 构造函数，传入以字符串形式的转移描述串。对描述串沿 delimiter 分割，前两个子串将转换成 int，之后的子串均是转移。描述串形如：1  2  a  b
     *
     * @param startId     开始节点的编号
     * @param delimiter   分隔符，
     * @param transitions 转移描述串列表
     */
    public SimpleScannerDFA(int startId, String delimiter, String... transitions) {
        // 初始化数据结构
        this.size = -1;
        this.startId = startId;
        this.adj = new Map2D<>();
        this.transitions = new HashSet<>();
        this.acceptType = new HashMap<>();
        this.defaultExceptionType = new HashMap<>();
        // 处理转移描述串
        for (String transition : transitions) { // 遍历描述串
            String[] splits = transition.split(delimiter); // 分割描述串
            if (splits.length < 2) {
                throw new IllegalArgumentException(String.format("描述串 %s 非法，缺少信息", transition));
            }
            int u, v; // 转移的起点和终点
            try {
                u = Integer.parseInt(splits[0]);
                v = Integer.parseInt(splits[1]);
            } catch (NumberFormatException ex) {
                throw new NumberFormatException(String.format("描述串 %s 非法，前两个子串不是整数", transition));
            }
            // 转移边
            for (int i = 2; i < splits.length; ++i) {
                if (splits[i].length() > 1) {
                    throw new IllegalArgumentException(String.format("描述串 %s 非法，转移边 %s 不是单个字符", transition, splits[i]));
                }
                int w = splits[i].charAt(0);
                this.addEdge(u, v, w);
            }
        }
    }

    /**
     * 添加转移边，若已存在择添加失败。另外，这会自动扩展相关数据结构。
     *
     * @param u 起点
     * @param v 终点
     * @param w 转移
     * @return 是否成功添加
     */
    private boolean addEdge(int u, int v, int w) {
        this.size = Math.max(this.size, u);
        this.size = Math.max(this.size, v);
        this.transitions.add(w);
        return this.adj.putIfAbsent(u, w, v) == null;
    }


    /**
     * 转换成 mermaid 语法的文本，以字符串形式给出
     */
    public String toMermaid() {
        StringBuilder ret = new StringBuilder("```mermaid\nflowchart LR\n");
        // 显示接受类型
        for (int u = this.startId(); u <= this.size(); ++u) {
            if (this.isAcceptable(u)) {
                ret.append(String.format("n%02d((\"%02d\"))\n", u, u));
            } else {
                ret.append(String.format("n%02d{\"%02d\"}\n", u, u));
            }
        }
        // 记录转移边
        Map2D<Integer, Integer, Set<Integer>> tmp = new Map2D<>();
        for (Map.Entry<Pair<Integer, Integer>, Integer> node : this.adj.entrySet()) {
            int u = node.getKey().getFirst(), w = node.getKey().getSecond(), v = node.getValue();
            tmp.putIfAbsent(u, v, new HashSet<>());
            tmp.get(u, v).add(w);
        }
        // 生成转移边的字符串
        for (Map.Entry<Pair<Integer, Integer>, Set<Integer>> node : tmp.entrySet()) {
            int u = node.getKey().getFirst(), v = node.getKey().getSecond();
            Set<Integer> transitions = node.getValue();
            StringBuilder builder = new StringBuilder();
            // 遍历转移集合
            for (int w : transitions) {
                if (w == '\n') builder.append("\\n");
                else if (w == '\t') builder.append("\\t");
                else if (w == '\"') builder.append("\\p");
                else if (w < 32) builder.append(String.format("\\%d", w));
                else builder.append((char) w);
                builder.append(" ");
            }
            // 适当的换行
            for (int i = builder.length() - builder.length() % 10; i > 0; i -= 10) {
                builder.insert(i, "\\n");
            }
            ret.append(String.format("n%02d--\"%s\"-->n%02d\n", u, builder, v));
        }
        ret.append("```");
        return ret.toString();
    }

}
